/**
 * 处理客户转交（包括转出和接收）
 */
import { getLogger, tuple, Service } from '@gnlab/utils'
import { MemSubject, mmap } from '@gnlab/utils/rx'
import { OrderedMap, OrderedSet } from 'immutable'
import omit from 'lodash/omit'
import { Subject } from 'rxjs'
import type { Services } from '@/services'
import type { BaseAccount } from '@/services/auth'
import type { RecvMessage } from '@/services/common/qywx-mqtt'
import type { QywxInfo } from '@/services/qywxs'
import type { ExternalContact } from '../externals'
import { getTransfers, transferReturn, transferBack, transfer } from './api'
import { formatTransferedExternal } from './definition'
import type { RawExternalTransfer, TransferedExternal } from './definition'

export * from './definition'

const logger = getLogger('services/externals/transfers')

type UpdateContact = ExternalContact | TransferedExternal

export class ExternalTransfers extends Service<Services> {
  private get communicate() {
    return this.services.mqtt.communicate
  }
  get auth() {
    return this.services.auth
  }
  private get account() {
    return this.services.auth.account$.value
  }
  private get accountId() {
    return this.account?.id ?? ''
  }
  private get qywxs() {
    return this.services.qywxs
  }

  registered() {
    void this.auth.accountId.then(() => {
      void this.auth.observing.accountIds.then(observingAccountIds => {
        if (observingAccountIds) return // 观察者模式下不开启转交功能

        this.initEventHandling()

        void this.load()
        setInterval(async () => this.load(), 60 * 1000) // 因为有“自动转交”功能存在，会有客户不经由当前页面被转交出去，因此定时刷新一下转交列表
      })
    })
  }

  /**
   * ==============================
   * 数据定义
   * ==============================
   */

  /**
   * 转交客户列表
   * - 包括“转出”和“接收”的客户
   * - “接收”的客户不会出现在联系人客户列表（services.externals.list）里
   * - “转出”的客户也不保证会出现在联系人客户列表里，因为这个列表里只有最近联系过的客户
   */
  private readonly _list = new MemSubject(OrderedMap<string, TransferedExternal>()) // contactId => transfered
  list = this._list.asStream()

  // “接收”到的客户列表
  receiveList = this.list.pipe(mmap(_list => _list.filter(item => !item.transfer.isGive)))

  // 向当前账号转交了客户的 wxid 列表
  senderWxids = this.receiveList.pipe(
    mmap(list => OrderedSet([...list.values()].map(v => v.ownerWxid))),
  )

  private readonly _received = new Subject<TransferedExternal>()
  private readonly _returned = new Subject<TransferedExternal>()
  private readonly _backed = new Subject<TransferedExternal>()

  readonly received = this._received.asObservable() // 推送新接收到的客户
  readonly backed = this._backed.asObservable() // 推送被发送方收回的客户
  readonly returned = this._returned.asObservable() // 推送被接收方退回的客户

  private readonly _loaded = new Subject<TransferedExternal[]>()
  readonly loaded = this._loaded.asObservable() // 每次执行 load() 推送结果

  /**
   * ==============================
   * 转交事件处理
   * ==============================
   */

  protected initEventHandling() {
    // 有企业微信转交出去时，重新加载转交客户列表（不然此企业微信之前转交出去的客户，会继续留在列表里）
    this.qywxs.transfers.transfered.subscribe(transfer => {
      if (transfer.fromAccountId === this.accountId) {
        void this.load()
      }
    })

    this.communicate.manage<RawExternalTransfer>(60001).subscribe(this.onReceived.bind(this))
    this.communicate.manage<RawExternalTransfer>(60002).subscribe(this.onBacked.bind(this))
    this.communicate.manage<RawExternalTransfer>(60003).subscribe(this.onReturned.bind(this))
  }

  /*
  接收到客户

  转交在后端有一个“查重逻辑”
  - wxid 和 transferWxId 都一样判断为重复转交
  - 如果这次的 fromUserId 和之前的一样，报错：不允许重复转交
  - 如果这次的 fromUserId 和之前的不一样，把之前的记录替换为现在的（也和新发起转交一样推送 MQTT）
  对于第三种“替换转交记录”的情况，收到 mqtt 推送时，list 里已有这个转交联系人，只是来源用户不一样。
  此时就需要用新生成的联系人数据代替老的，不能做“联系人已存在，不再更新列表”的优化。
  */
  protected onReceived(message: RecvMessage<RawExternalTransfer>) {
    logger.debug('收到转交客户', message)
    const contact = formatTransferedExternal(message.data, this.accountId)
    this._list.next(this.list.value.set(contact.id, contact))
    this._received.next(contact)
  }

  // 客户发送方“收回”了客户
  protected onBacked(message: RecvMessage<RawExternalTransfer>) {
    logger.debug('转交客户被收回', message)
    const contact = formatTransferedExternal(message.data, this.accountId)
    if (this.list.value.get(contact.id)?.transfer.isGive === false) {
      this._list.next(this.list.value.delete(contact.id))
      this._backed.next(contact)
    }
  }

  // 客户接收方“退回”了客户
  protected onReturned(message: RecvMessage<RawExternalTransfer>) {
    logger.debug('转交客户被退回', message)
    const contact = formatTransferedExternal(message.data, this.accountId)
    if (this.list.value.get(contact.id)?.transfer.isGive === true) {
      this._list.next(this.list.value.delete(contact.id))
      this._returned.next(contact)
    }
  }

  /**
   * ==============================
   * 转交操作
   * ==============================
   */

  // 加载转交客户列表
  async load() {
    const res = await getTransfers(this.accountId)
    if (res.success) {
      const list = OrderedMap(res.data.map(item => tuple(item.id, item)))
      this._list.next(list)
      this._loaded.next(res.data)
    } else {
      logger.warn('转交客户列表加载失败', res.error)
    }
  }

  /**
   * 更新转交列表中指定客户的数据
   * - 仅限 service 层调用
   * - 仅更新，不添加（转交客户只会因转交事件而增加，不会因外部其他原因添加）
   * - 转出或接收的客户都可进行更新
   *   且对于“转出”的客户，更新其在客户列表里的资料时，必须同步更新其在转交列表里的资料
   */
  update(inputContacts: UpdateContact | UpdateContact[], goTop = false) {
    const contacts = Array.isArray(inputContacts) ? inputContacts : [inputContacts]
    const updates: TransferedExternal[] = []
    for (const contact of contacts) {
      const exist = this.list.value.get(contact.id)
      if (!exist) {
        void logger.warn(`客户 ${contact.id} 不存在于转交客户列表中，无法更新其在转交列表里的资料`)
        continue
      }
      const updated = { ...exist, ...('transfer' in contact ? omit(contact, 'transfer') : contact) }
      updates.push(updated)
    }

    if (updates.length) {
      const list = goTop
        ? OrderedMap(updates.map(v => [v.id, v])).concat(
            this.list.value.deleteAll(updates.map(v => v.id)),
          )
        : this.list.value.concat(updates.map(v => [v.id, v]))
      this._list.next(list)
    }
  }

  // 转交一个客户
  async transfer(params: {
    contact: ExternalContact // 要转交的联系人
    qywx?: Pick<QywxInfo, 'friendlyName' | 'corpid'> // 此联系人归属的企业微信信息
    fromAccount?: Pick<BaseAccount, 'id' | 'name'> // 从哪个账号处转交出去（此联系人必须归属此账号下的企业微信），默认当前账号
    targetAccount: Pick<BaseAccount, 'id' | 'name'> // 转交给哪个账号
    remark: string // 转交说明
  }) {
    const {
      contact,
      qywx = (this.qywxs.alives.value.get(contact.ownerWxid) ?? {}) as unknown as QywxInfo,
      fromAccount = this.account!,
      targetAccount,
      remark,
    } = params
    const res = await transfer({ contact, qywx, fromAccount, targetAccount, remark })
    void this.load()
    return res
  }

  // 作为发送方收回客户
  async makeBack(contactId: string) {
    const contact = this.list.value.get(contactId)
    if (!contact || !contact.transfer.isGive) return

    const res = await transferBack(contact.transfer.id)
    if (res.success) this._list.next(this.list.value.delete(contactId))
    return res
  }

  // 作为接收方退回客户
  async makeReturn(contactId: string) {
    const contact = this.list.value.get(contactId)
    if (!contact || contact.transfer.isGive) return

    const res = await transferReturn(contact.transfer.id)
    if (res.success) this._list.next(this.list.value.delete(contactId))
    return res
  }
}
